<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>OCR文字识别工具</title>
    <!-- Tesseract.js CDN -->
    <script src="./ocr/tesseract.min.js"></script>

    <style>
      * {
        box-sizing: border-box;
        margin: 0;
        padding: 0;
        font-family: 'SF Pro Display', 'PingFang SC', 'Helvetica Neue', Arial,
          sans-serif;
      }

      :root {
        --primary-bg: #e6e7ee;
        --secondary-bg: #ffffff;
        --shadow-light: #ffffff;
        --shadow-dark: #d1d9e6;
        --accent-color: #667eea;
        --accent-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        --success-color: #4ecdc4;
        --danger-color: #ff6b6b;
        --warning-color: #feca57;
        --text-primary: #2d3748;
        --text-secondary: #718096;
        --glass-bg: rgba(255, 255, 255, 0.25);
        --glass-border: rgba(255, 255, 255, 0.18);
        --border-radius: 20px;
        --shadow-large: 15px 15px 30px var(--shadow-dark),
          -15px -15px 30px var(--shadow-light);
      }

      body {
        background: var(--primary-bg);
        color: var(--text-primary);
        min-height: 100vh;
        padding: 20px;
        display: flex;
        flex-direction: column;
        align-items: center;
        transition: all 0.3s ease;
        animation: pageLoad 0.8s ease;
      }

      @keyframes pageLoad {
        from {
          opacity: 0;
          transform: translateY(20px);
        }
        to {
          opacity: 1;
          transform: translateY(0);
        }
      }

      .container {
        width: 100%;
        max-width: 1200px;
        background: var(--primary-bg);
        border-radius: 30px;
        padding: 40px;
        box-shadow: 20px 20px 60px var(--shadow-dark),
          -20px -20px 60px var(--shadow-light);
        margin-top: 20px;
        transition: all 0.3s ease;
        animation: containerFloat 1s ease 0.2s both;
      }

      @keyframes containerFloat {
        from {
          opacity: 0;
          transform: translateY(30px) scale(0.95);
        }
        to {
          opacity: 1;
          transform: translateY(0) scale(1);
        }
      }

      .container:hover {
        box-shadow: 25px 25px 70px var(--shadow-dark),
          -25px -25px 70px var(--shadow-light);
      }

      header {
        text-align: center;
        margin-bottom: 40px;
        padding: 30px;
        background: var(--primary-bg);
        border-radius: 25px;
        box-shadow: 10px 10px 20px var(--shadow-dark),
          -10px -10px 20px var(--shadow-light);
        animation: headerSlide 0.8s ease 0.4s both;
      }

      @keyframes headerSlide {
        from {
          opacity: 0;
          transform: translateY(-30px);
        }
        to {
          opacity: 1;
          transform: translateY(0);
        }
      }

      h1 {
        font-size: 3.2rem;
        margin-bottom: 15px;
        background: var(--accent-gradient);
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
        background-clip: text;
        font-weight: 700;
        animation: titleGlow 3s ease-in-out infinite alternate;
      }

      @keyframes titleGlow {
        from {
          filter: brightness(1);
        }
        to {
          filter: brightness(1.2);
        }
      }

      .subtitle {
        font-size: 1.3rem;
        color: var(--text-secondary);
        margin-bottom: 30px;
        font-weight: 500;
      }

      .main-content {
        display: grid;
        grid-template-columns: 2fr 1fr;
        gap: 30px;
        margin-bottom: 40px;
        animation: panelsSlide 0.8s ease 0.6s both;
        align-items: start;
      }

      @media (max-width: 1024px) {
        .main-content {
          grid-template-columns: 1fr;
          gap: 25px;
        }

        .control-panel {
          order: 2;
        }

        .image-panel {
          order: 1;
        }
      }

      @media (max-width: 768px) {
        .main-content {
          gap: 20px;
        }

        .image-panel,
        .control-panel {
          padding: 20px;
        }

        .ocr-settings,
        .result-panel {
          padding: 20px;
        }

        .action-buttons {
          flex-direction: column;
          gap: 10px;
        }
      }

      @keyframes panelsSlide {
        from {
          opacity: 0;
          transform: translateX(-20px);
        }
        to {
          opacity: 1;
          transform: translateX(0);
        }
      }

      .image-panel {
        background: var(--primary-bg);
        border-radius: 25px;
        padding: 30px;
        box-shadow: 12px 12px 24px var(--shadow-dark),
          -12px -12px 24px var(--shadow-light);
        transition: all 0.3s ease;
        position: relative;
        overflow: hidden;
        display: flex;
        flex-direction: column;
        gap: 25px;
      }

      .image-panel::before {
        content: '';
        position: absolute;
        top: 0;
        left: -100%;
        width: 100%;
        height: 100%;
        background: linear-gradient(
          90deg,
          transparent,
          rgba(255, 255, 255, 0.1),
          transparent
        );
        transition: left 0.8s;
      }

      .image-panel:hover::before {
        left: 100%;
      }

      .image-panel:hover {
        transform: translateY(-5px);
        box-shadow: 16px 16px 32px var(--shadow-dark),
          -16px -16px 32px var(--shadow-light);
      }

      .control-panel {
        background: var(--primary-bg);
        border-radius: 25px;
        padding: 30px;
        box-shadow: 12px 12px 24px var(--shadow-dark),
          -12px -12px 24px var(--shadow-light);
        transition: all 0.3s ease;
        position: relative;
        overflow: hidden;
        display: flex;
        flex-direction: column;
        gap: 25px;
      }

      .ocr-settings,
      .result-panel {
        background: rgba(255, 255, 255, 0.02);
        border-radius: 20px;
        padding: 25px;
        border: 1px solid rgba(255, 255, 255, 0.05);
        box-shadow: inset 6px 6px 12px var(--shadow-dark),
          inset -6px -6px 12px var(--shadow-light);
        transition: all 0.3s ease;
        margin-top: 0;
      }

      .image-panel .ocr-settings {
        background: rgba(255, 255, 255, 0.05);
        border: 1px solid rgba(255, 255, 255, 0.1);
      }

      .ocr-settings:hover,
      .result-panel:hover {
        transform: translateY(-2px);
        box-shadow: inset 8px 8px 16px var(--shadow-dark),
          inset -8px -8px 16px var(--shadow-light),
          0 8px 20px rgba(0, 0, 0, 0.1);
      }

      .action-buttons {
        display: flex;
        gap: 15px;
        justify-content: center;
        flex-wrap: wrap;
        margin-top: 10px;
      }

      .control-panel::before {
        content: '';
        position: absolute;
        top: 0;
        left: -100%;
        width: 100%;
        height: 100%;
        background: linear-gradient(
          90deg,
          transparent,
          rgba(255, 255, 255, 0.1),
          transparent
        );
        transition: left 0.8s;
      }

      .control-panel:hover::before {
        left: 100%;
      }

      .control-panel:hover {
        transform: translateY(-5px);
        box-shadow: 16px 16px 32px var(--shadow-dark),
          -16px -16px 32px var(--shadow-light);
      }

      .panel-title {
        font-size: 1.6rem;
        margin-bottom: 25px;
        color: var(--accent-color);
        display: flex;
        align-items: center;
        font-weight: 600;
      }

      .panel-title::before {
        content: attr(data-icon);
        margin-right: 12px;
        font-size: 1.8rem;
        animation: iconBounce 2s ease-in-out infinite;
      }

      @keyframes iconBounce {
        0%,
        100% {
          transform: scale(1);
        }
        50% {
          transform: scale(1.1);
        }
      }

      .file-upload {
        border: 3px dashed var(--accent-color);
        border-radius: 25px;
        padding: 50px 40px;
        text-align: center;
        transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
        margin-bottom: 25px;
        cursor: pointer;
        background: var(--primary-bg);
        box-shadow: inset 8px 8px 16px var(--shadow-dark),
          inset -8px -8px 16px var(--shadow-light);
        position: relative;
        overflow: hidden;
      }

      .file-upload:hover {
        border-color: var(--success-color);
        transform: translateY(-5px) scale(1.02);
        box-shadow: inset 10px 10px 20px var(--shadow-dark),
          inset -10px -10px 20px var(--shadow-light),
          0 15px 40px rgba(102, 126, 234, 0.25), 0 5px 15px rgba(0, 0, 0, 0.1);
      }

      .file-upload.dragover {
        border-color: var(--success-color);
        background: rgba(78, 205, 196, 0.05);
        transform: scale(1.05);
        box-shadow: inset 12px 12px 24px var(--shadow-dark),
          inset -12px -12px 24px var(--shadow-light),
          0 20px 50px rgba(78, 205, 196, 0.3);
        animation: dragPulse 1s ease-in-out infinite;
      }

      @keyframes dragPulse {
        0%,
        100% {
          border-color: var(--success-color);
        }
        50% {
          border-color: var(--accent-color);
        }
      }

      .file-upload-content {
        position: relative;
        z-index: 1;
      }

      .file-upload-icon {
        font-size: 3.5rem;
        margin-bottom: 20px;
        display: block;
        animation: iconFloat 3s ease-in-out infinite;
        filter: drop-shadow(0 4px 8px rgba(102, 126, 234, 0.2));
      }

      @keyframes iconFloat {
        0%,
        100% {
          transform: translateY(0);
        }
        50% {
          transform: translateY(-10px);
        }
      }

      .file-upload-text {
        font-size: 1.4rem;
        font-weight: 600;
        color: var(--text-primary);
        margin-bottom: 10px;
        background: var(--accent-gradient);
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
        background-clip: text;
      }

      .file-upload-subtext {
        font-size: 1.1rem;
        color: var(--text-secondary);
        margin-bottom: 20px;
        font-weight: 500;
      }

      .file-upload-formats {
        font-size: 0.9rem;
        color: var(--accent-color);
        font-weight: 500;
        padding: 8px 16px;
        background: rgba(102, 126, 234, 0.1);
        border-radius: 20px;
        display: inline-block;
        margin-bottom: 20px;
      }

      .file-upload input[type='file'] {
        position: absolute;
        width: 100%;
        height: 100%;
        top: 0;
        left: 0;
        opacity: 0;
        cursor: pointer;
        z-index: 2;
      }

      .image-container {
        position: relative;
        width: 100%;
        background: var(--primary-bg);
        border-radius: 20px;
        overflow: hidden;
        margin-top: 20px;
        box-shadow: inset 10px 10px 20px var(--shadow-dark),
          inset -10px -10px 20px var(--shadow-light);
        transition: all 0.3s ease;
      }

      .image-container:hover {
        box-shadow: inset 12px 12px 24px var(--shadow-dark),
          inset -12px -12px 24px var(--shadow-light);
      }

      img {
        width: 100%;
        height: auto;
        display: block;
        border-radius: 15px;
      }

      .selection-overlay {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        pointer-events: none;
        z-index: 10;
      }

      /* 移动端触摸优化 */
      .canvas-container {
        touch-action: none;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
      }

      .canvas-wrapper {
        position: relative;
        display: inline-block;
        max-width: 100%;
      }

      /* 防止触摸时的默认行为 */
      canvas {
        -webkit-touch-callout: none;
        -webkit-user-select: none;
        -khtml-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
        -webkit-tap-highlight-color: transparent;
      }

      @media (max-width: 768px) {
        .canvas-container {
          max-width: 100%;
          overflow-x: auto;
        }
        
        .selection-modal {
          padding: 10px;
        }
        
        .canvas-wrapper {
          margin: 10px 0;
        }
        
        .selection-region {
          border-width: 4px; /* 触摸时更粗的边框 */
        }
        
        .region-handle {
          width: 20px;
          height: 20px;
        }
      }

      .selection-region {
        position: absolute;
        border: 3px solid var(--accent-color);
        background: rgba(102, 126, 234, 0.15);
        cursor: move;
        pointer-events: all;
        min-width: 50px;
        min-height: 50px;
        box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.3),
          inset 0 0 0 1px rgba(255, 255, 255, 0.3),
          0 0 20px rgba(102, 126, 234, 0.4);
        transition: all 0.3s ease;
        touch-action: none;
        border-radius: 8px;
        user-select: none;
      }

      .selection-region:hover {
        border-color: var(--warning-color);
        background: rgba(254, 202, 87, 0.15);
        box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.4),
          inset 0 0 0 1px rgba(255, 255, 255, 0.4),
          0 0 30px rgba(254, 202, 87, 0.5);
      }

      .selection-region.selected {
        border-color: var(--success-color);
        background: rgba(78, 205, 196, 0.15);
        box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.4),
          inset 0 0 0 1px rgba(255, 255, 255, 0.4),
          0 0 30px rgba(78, 205, 196, 0.5);
      }

      .region-handle {
        position: absolute;
        width: 16px;
        height: 16px;
        background: var(--accent-color);
        border: 3px solid white;
        border-radius: 50%;
        cursor: pointer;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
        transition: all 0.3s ease;
        z-index: 20;
        touch-action: none;
      }

      .region-handle:hover {
        transform: scale(1.2);
        box-shadow: 0 4px 16px rgba(102, 126, 234, 0.4);
      }

      .region-handle.nw {
        top: -8px;
        left: -8px;
        cursor: nw-resize;
      }
      .region-handle.ne {
        top: -8px;
        right: -8px;
        cursor: ne-resize;
      }
      .region-handle.sw {
        bottom: -8px;
        left: -8px;
        cursor: sw-resize;
      }
      .region-handle.se {
        bottom: -8px;
        right: -8px;
        cursor: se-resize;
      }
      .region-handle.n {
        top: -8px;
        left: 50%;
        transform: translateX(-50%);
        cursor: n-resize;
      }
      .region-handle.s {
        bottom: -8px;
        left: 50%;
        transform: translateX(-50%);
        cursor: s-resize;
      }
      .region-handle.w {
        top: 50%;
        left: -8px;
        transform: translateY(-50%);
        cursor: w-resize;
      }
      .region-handle.e {
        top: 50%;
        right: -8px;
        transform: translateY(-50%);
        cursor: e-resize;
      }

      .region-label {
        position: absolute;
        top: -30px;
        left: 0;
        background: var(--accent-color);
        color: white;
        padding: 4px 8px;
        border-radius: 12px;
        font-size: 0.8rem;
        font-weight: 600;
        white-space: nowrap;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
      }

      .region-delete {
        position: absolute;
        top: -12px;
        right: -12px;
        width: 24px;
        height: 24px;
        background: var(--danger-color);
        color: white;
        border: none;
        border-radius: 50%;
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 0.8rem;
        font-weight: bold;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
        transition: all 0.3s ease;
        z-index: 21;
      }

      .region-delete:hover {
        transform: scale(1.1);
        box-shadow: 0 4px 16px rgba(255, 107, 107, 0.4);
      }

      .form-group {
        margin-bottom: 25px;
      }

      label {
        display: block;
        margin-bottom: 10px;
        color: var(--text-secondary);
        font-weight: 500;
        font-size: 0.95rem;
      }

      input,
      select,
      textarea {
        width: 100%;
        padding: 14px 18px;
        background: var(--primary-bg);
        border: none;
        border-radius: 15px;
        color: var(--text-primary);
        font-size: 1rem;
        font-weight: 500;
        box-shadow: inset 6px 6px 12px var(--shadow-dark),
          inset -6px -6px 12px var(--shadow-light);
        transition: all 0.3s ease;
        resize: vertical;
      }

      input:focus,
      select:focus,
      textarea:focus {
        outline: none;
        box-shadow: inset 8px 8px 16px var(--shadow-dark),
          inset -8px -8px 16px var(--shadow-light),
          0 0 0 3px rgba(102, 126, 234, 0.2);
      }

      button {
        background: var(--primary-bg);
        color: var(--text-primary);
        border: none;
        padding: 14px 28px;
        border-radius: 15px;
        cursor: pointer;
        font-weight: 600;
        transition: all 0.3s ease;
        font-size: 1rem;
        flex-shrink: 0;
        box-shadow: 8px 8px 16px var(--shadow-dark),
          -8px -8px 16px var(--shadow-light);
        position: relative;
        overflow: hidden;
        min-width: 140px;
      }

      .form-group button,
      .ocr-settings button {
        width: 100%;
        margin-bottom: 15px;
      }

      .action-buttons button {
        flex: 1;
        min-width: 180px;
        margin-bottom: 0;
      }

      button::before {
        content: '';
        position: absolute;
        top: 0;
        left: -100%;
        width: 100%;
        height: 100%;
        background: linear-gradient(
          90deg,
          transparent,
          rgba(255, 255, 255, 0.2),
          transparent
        );
        transition: left 0.5s;
      }

      button:hover::before {
        left: 100%;
      }

      button:hover {
        transform: translateY(-3px);
        box-shadow: 12px 12px 24px var(--shadow-dark),
          -12px -12px 24px var(--shadow-light);
      }

      button:active {
        transform: translateY(0);
        box-shadow: inset 6px 6px 12px var(--shadow-dark),
          inset -6px -6px 12px var(--shadow-light);
      }

      button:disabled {
        opacity: 0.6;
        cursor: not-allowed;
        transform: none;
        box-shadow: 4px 4px 8px var(--shadow-dark),
          -4px -4px 8px var(--shadow-light);
      }

      .btn-primary {
        background: var(--accent-gradient);
        color: white;
        padding: 16px 32px;
        font-size: 1.1rem;
        box-shadow: 8px 8px 16px var(--shadow-dark),
          -8px -8px 16px var(--shadow-light),
          inset 0 1px 0 rgba(255, 255, 255, 0.2);
      }

      .btn-primary:hover {
        box-shadow: 12px 12px 24px var(--shadow-dark),
          -12px -12px 24px var(--shadow-light),
          inset 0 1px 0 rgba(255, 255, 255, 0.2),
          0 0 30px rgba(102, 126, 234, 0.3);
      }

      .btn-success {
        background: linear-gradient(
          135deg,
          var(--success-color) 0%,
          #44a08d 100%
        );
        color: white;
        box-shadow: 8px 8px 16px var(--shadow-dark),
          -8px -8px 16px var(--shadow-light),
          inset 0 1px 0 rgba(255, 255, 255, 0.2);
      }

      .btn-warning {
        background: linear-gradient(
          135deg,
          var(--warning-color) 0%,
          #f39c12 100%
        );
        color: white;
        box-shadow: 8px 8px 16px var(--shadow-dark),
          -8px -8px 16px var(--shadow-light),
          inset 0 1px 0 rgba(255, 255, 255, 0.2);
      }

      .btn-danger {
        background: linear-gradient(
          135deg,
          var(--danger-color) 0%,
          #c0392b 100%
        );
        color: white;
        box-shadow: 8px 8px 16px var(--shadow-dark),
          -8px -8px 16px var(--shadow-light),
          inset 0 1px 0 rgba(255, 255, 255, 0.2);
      }

      .progress-container {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.8);
        display: none;
        justify-content: center;
        align-items: center;
        z-index: 1000;
      }

      .progress-content {
        background: var(--primary-bg);
        padding: 40px;
        border-radius: 25px;
        text-align: center;
        box-shadow: 20px 20px 60px var(--shadow-dark),
          -20px -20px 60px var(--shadow-light);
        min-width: 400px;
      }

      .progress-bar {
        width: 100%;
        height: 8px;
        background: var(--primary-bg);
        border-radius: 4px;
        overflow: hidden;
        margin: 20px 0;
        box-shadow: inset 4px 4px 8px var(--shadow-dark),
          inset -4px -4px 8px var(--shadow-light);
      }

      .progress-fill {
        height: 100%;
        background: var(--accent-gradient);
        width: 0%;
        transition: width 0.3s ease;
        border-radius: 4px;
      }

      .status-message {
        position: fixed;
        top: 20px;
        right: 20px;
        padding: 15px 25px;
        border-radius: 15px;
        color: white;
        font-weight: 600;
        z-index: 1001;
        transform: translateX(400px);
        transition: transform 0.3s ease;
        box-shadow: 8px 8px 16px rgba(0, 0, 0, 0.2);
      }

      .status-message.show {
        transform: translateX(0);
      }

      .status-message.success {
        background: var(--success-color);
      }

      .status-message.error {
        background: var(--danger-color);
      }

      .status-message.info {
        background: var(--accent-color);
      }

      .result-text {
        min-height: 200px;
        font-family: 'Courier New', monospace;
        line-height: 1.6;
      }

      .confidence-info {
        margin-top: 15px;
        padding: 10px;
        background: rgba(102, 126, 234, 0.1);
        border-radius: 10px;
        font-size: 0.9rem;
        color: var(--text-secondary);
      }

      .region-info {
        background: rgba(255, 255, 255, 0.05);
        border-radius: 15px;
        padding: 15px;
        margin-bottom: 15px;
        border: 1px solid rgba(255, 255, 255, 0.1);
      }

      .region-name {
        font-weight: 600;
        color: var(--accent-color);
        margin-bottom: 5px;
      }

      .region-coords {
        font-size: 0.9rem;
        color: var(--text-secondary);
        margin-bottom: 10px;
      }

      .region-actions {
        display: flex;
        gap: 10px;
      }

      .btn-small {
        padding: 8px 16px;
        font-size: 0.85rem;
        min-width: auto;
      }

      .panel-title {
        display: flex;
        align-items: center;
        justify-content: space-between;
      }

      .panel-title button {
        margin-left: auto;
        flex-shrink: 0;
      }

      /* Canvas 样式 */
      .canvas-container {
        margin: 20px 0;
        border-radius: 15px;
        overflow: hidden;
        box-shadow: var(--shadow-large);
        position: relative;
        display: flex;
        justify-content: center;
        align-items: center;
        background: var(--secondary-bg);
      }

      #imageCanvas {
        max-width: 100%;
        max-height: 100%;
        cursor: default;
        display: block;
        border-radius: 15px;
        transition: cursor 0.2s ease;
      }

      #imageCanvas:hover {
        cursor: crosshair;
      }

      .selection-overlay {
        position: absolute;
        border: 2px dashed #00d4ff;
        background: rgba(0, 212, 255, 0.1);
        pointer-events: none;
        z-index: 10;
        border-radius: 4px;
        box-shadow: 0 0 10px rgba(0, 212, 255, 0.3);
        animation: selection-pulse 1.5s ease-in-out infinite alternate;
      }

      @keyframes selection-pulse {
        0% {
          box-shadow: 0 0 10px rgba(0, 212, 255, 0.3);
        }
        100% {
          box-shadow: 0 0 20px rgba(0, 212, 255, 0.6);
        }
      }

      /* 预览图片样式 */
      #previewImage {
        max-width: 100%;
        max-height: 500px;
        cursor: pointer;
        display: block;
        border-radius: 15px;
        transition: all 0.3s ease;
        box-shadow: var(--shadow-large);
        overflow: auto;
      }

      #previewImage:hover {
        transform: scale(1.02);
        box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
      }

      /* 弹框样式 */
      .modal-overlay {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.8);
        display: flex;
        justify-content: center;
        align-items: center;
        z-index: 1000;
        animation: modalFadeIn 0.3s ease;
      }

      @keyframes modalFadeIn {
        from {
          opacity: 0;
        }
        to {
          opacity: 1;
        }
      }

      .modal-content {
        background: var(--primary-bg);
        border-radius: 25px;
        padding: 0;
        box-shadow: 20px 20px 60px var(--shadow-dark),
          -20px -20px 60px var(--shadow-light);
        max-width: 90vw;
        max-height: 90vh;
        overflow: hidden;
        animation: modalSlideIn 0.3s ease;
      }

      @keyframes modalSlideIn {
        from {
          transform: scale(0.9) translateY(-20px);
          opacity: 0;
        }
        to {
          transform: scale(1) translateY(0);
          opacity: 1;
        }
      }

      .modal-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 25px 30px;
        border-bottom: 1px solid rgba(255, 255, 255, 0.1);
        background: rgba(255, 255, 255, 0.05);
      }

      .modal-header h3 {
        margin: 0;
        color: var(--accent-color);
        font-size: 1.5rem;
        font-weight: 600;
      }

      .modal-close {
        background: none;
        border: none;
        font-size: 2rem;
        color: var(--text-secondary);
        cursor: pointer;
        padding: 0;
        width: 40px;
        height: 40px;
        border-radius: 50%;
        display: flex;
        align-items: center;
        justify-content: center;
        transition: all 0.3s ease;
        box-shadow: none;
      }

      .modal-close:hover {
        background: rgba(255, 107, 107, 0.1);
        color: var(--danger-color);
        transform: scale(1.1);
      }

      .modal-body {
        padding: 30px;
        max-height: 60vh;
        overflow: auto;
      }

      .modal-body .canvas-container {
        margin: 0;
        max-height: none;
      }

      .modal-footer {
        display: flex;
        justify-content: flex-end;
        gap: 15px;
        padding: 25px 30px;
        border-top: 1px solid rgba(255, 255, 255, 0.1);
        background: rgba(255, 255, 255, 0.05);
      }

      .btn-secondary {
        background: var(--primary-bg);
        color: var(--text-secondary);
        border: 1px solid rgba(255, 255, 255, 0.2);
      }

      .btn-secondary:hover {
        background: rgba(255, 255, 255, 0.1);
        color: var(--text-primary);
      }

      .cropped-regions {
        display: flex;
        flex-wrap: wrap;
        gap: 15px;
        margin-top: 20px;
      }

      .cropped-region {
        position: relative;
        border: 2px solid var(--accent-color);
        border-radius: 10px;
        overflow: hidden;
        background: var(--secondary-bg);
        box-shadow: 8px 8px 16px var(--shadow-dark),
          -8px -8px 16px var(--shadow-light);
        transition: all 0.3s ease;
      }

      .cropped-region:hover {
        transform: translateY(-2px);
        box-shadow: 12px 12px 20px var(--shadow-dark),
          -12px -12px 20px var(--shadow-light);
      }

      .cropped-region img {
        display: block;
        max-width: 200px;
        max-height: 150px;
        object-fit: contain;
      }

      .cropped-region-label {
        position: absolute;
        top: 5px;
        left: 5px;
        background: var(--accent-color);
        color: white;
        padding: 2px 8px;
        border-radius: 12px;
        font-size: 0.8rem;
        font-weight: 600;
      }

      .cropped-region-delete {
        position: absolute;
        top: 5px;
        right: 5px;
        background: var(--danger-color);
        color: white;
        border: none;
        width: 24px;
        max-width: 24px;
        min-width: 24px;
        height: 24px;
        border-radius: 50%;
        cursor: pointer;
        font-size: 14px;
        display: flex;
        align-items: center;
        justify-content: center;
        transition: all 0.2s ease;
      }

      .cropped-region-delete:hover {
        background: #e74c3c;
        transform: scale(1.1);
      }
    </style>
  </head>
  <body>
    <!-- 区域选择弹框 -->
    <div class="modal-overlay" id="modalOverlay" style="display: none">
      <div class="modal-content">
        <div class="modal-header">
          <h3>选择识别区域</h3>
          <button class="modal-close" id="modalClose">×</button>
        </div>
        <div class="modal-body">
          <div class="canvas-container" id="canvasContainer">
            <canvas id="imageCanvas"></canvas>
            <div
              class="selection-overlay"
              id="selectionOverlay"
              style="display: none"
            ></div>
          </div>
        </div>
        <div class="modal-footer">
          <button class="btn-secondary" id="cancelSelection">取消</button>
          <button class="btn-primary" id="confirmSelection">确认并识别</button>
        </div>
      </div>
    </div>

    <div class="container">
      <header>
        <h1>OCR文字识别工具</h1>
        <p class="subtitle">
          上传图片，使用专业裁剪工具选择区域，智能提取文字内容
        </p>
      </header>

      <div class="main-content">
        <div class="image-panel">
          <div class="panel-title" data-icon="🖼️">
            <span>图片预览</span>
            <button id="pasteFromClipboardBtn" class="btn-small" style="font-size: 0.9rem; padding: 8px 16px; min-width: auto;">📋 粘贴剪切板</button>
          </div>

          <div class="file-upload" id="fileUpload">
            <div class="file-upload-content">
              <span class="file-upload-icon">📁</span>
              <div class="file-upload-text">点击或拖拽上传图片</div>
              <div class="file-upload-subtext">支持多种图片格式</div>
              <div class="file-upload-formats">JPG, PNG, GIF, BMP, WEBP</div>
            </div>
            <input type="file" id="imageInput" accept="image/*" />
          </div>

          <div
            class="image-container"
            id="imageContainer"
            style="display: none"
          >
            <img id="previewImage" alt="预览图片" />
          </div>

          <div class="ocr-settings">
            <div class="form-group">
              <button id="cropAndRecognizeBtn" disabled>裁剪并识别</button>
            </div>
            <div class="form-group">
              <button id="resetCanvasBtn" class="btn-warning" disabled>
                重置画布
              </button>
            </div>
          </div>
        </div>

        <div class="control-panel">
          <div class="ocr-settings">
            <div class="panel-title" data-icon="⚙️">识别设置</div>

            <div class="form-group">
              <label for="languageSelect">识别语言</label>
              <select id="languageSelect">
                <option value="chi_sim">简体中文</option>
                <option value="chi_tra">繁体中文</option>
                <option value="eng">英文</option>
                <option value="chi_sim+eng">中英混合</option>
              </select>
            </div>

            <div class="form-group">
              <label for="psmSelect">页面分割模式</label>
              <select id="psmSelect">
                <option value="6">单一文本块</option>
                <option value="7">单行文本</option>
                <option value="8">单个单词</option>
                <option value="13">原始行，无特定顺序</option>
              </select>
            </div>
          </div>

          <div class="result-panel">
            <div class="panel-title" data-icon="📝">识别结果</div>

            <div class="form-group">
              <label for="resultText">识别文本</label>
              <textarea
                id="resultText"
                class="result-text"
                placeholder="识别结果将显示在这里..."
                readonly
              ></textarea>
            </div>

            <div
              class="confidence-info"
              id="confidenceInfo"
              style="display: none"
            >
              <strong>识别置信度:</strong> <span id="confidenceValue">0%</span>
            </div>

            <div class="action-buttons">
              <button id="copyBtn" class="btn-success" disabled>
                复制文本
              </button>
              <button id="downloadBtn" class="btn-warning" disabled>
                下载结果
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>

    <!-- 进度提示 -->
    <div class="progress-container" id="progressContainer">
      <div class="progress-content">
        <h3 id="progressTitle">正在识别...</h3>
        <div class="progress-bar">
          <div class="progress-fill" id="progressFill"></div>
        </div>
        <p id="progressText">准备中...</p>
      </div>
    </div>

    <!-- 状态消息 -->
    <div class="status-message" id="statusMessage"></div>

    <script>
      class OCRTool {
        constructor() {
          this.imageFile = null
          this.canvas = null
          this.ctx = null
          this.worker = null
          this.isSelecting = false
          this.startX = 0
          this.startY = 0
          this.endX = 0
          this.endY = 0
          this.imageData = null

          this.initElements()
          this.bindEvents()
          this.initTesseract()
        }

        initElements() {
          // 文件上传相关
          this.fileUpload = document.getElementById('fileUpload')
          this.imageInput = document.getElementById('imageInput')
          this.imageContainer = document.getElementById('imageContainer')
          this.previewImage = document.getElementById('previewImage')

          // 弹框相关
          this.modalOverlay = document.getElementById('modalOverlay')
          this.modalClose = document.getElementById('modalClose')
          this.cancelSelection = document.getElementById('cancelSelection')
          this.confirmSelection = document.getElementById('confirmSelection')

          // Canvas相关（在弹框中）
          this.canvasContainer = document.getElementById('canvasContainer')
          this.canvas = document.getElementById('imageCanvas')
          this.ctx = this.canvas.getContext('2d')
          this.selectionOverlay = document.getElementById('selectionOverlay')

          // 控制按钮
          this.cropAndRecognizeBtn = document.getElementById(
            'cropAndRecognizeBtn'
          )
          this.resetCanvasBtn = document.getElementById('resetCanvasBtn')
          this.copyBtn = document.getElementById('copyBtn')
          this.downloadBtn = document.getElementById('downloadBtn')
          this.pasteFromClipboardBtn = document.getElementById('pasteFromClipboardBtn')

          // 设置选项
          this.languageSelect = document.getElementById('languageSelect')
          this.psmSelect = document.getElementById('psmSelect')

          // 结果显示
          this.resultText = document.getElementById('resultText')
          this.confidenceInfo = document.getElementById('confidenceInfo')
          this.confidenceValue = document.getElementById('confidenceValue')

          // 进度提示
          this.progressContainer = document.getElementById('progressContainer')
          this.progressFill = document.getElementById('progressFill')
          this.progressText = document.getElementById('progressText')
          this.progressTitle = document.getElementById('progressTitle')

          // 状态消息
          this.statusMessage = document.getElementById('statusMessage')
        }

        bindEvents() {
          // 文件上传事件
          this.fileUpload.addEventListener('click', (e) => {
            // 如果点击的是input元素本身，不需要再次触发
            if (e.target === this.imageInput) {
              return
            }
            this.imageInput.click()
          })
          this.imageInput.addEventListener('change', (e) => {
            this.handleFileSelect(e)
            // 清空input的值，允许重复选择同一个文件
            e.target.value = ''
          })

          // 拖拽上传
          this.fileUpload.addEventListener('dragover', (e) => {
            e.preventDefault()
            this.fileUpload.classList.add('dragover')
          })

          this.fileUpload.addEventListener('dragleave', () => {
            this.fileUpload.classList.remove('dragover')
          })

          this.fileUpload.addEventListener('drop', (e) => {
            e.preventDefault()
            this.fileUpload.classList.remove('dragover')
            const files = e.dataTransfer.files
            if (files.length > 0) {
              this.handleFile(files[0])
            }
          })

          // 图片点击事件（打开弹框）
          this.previewImage.addEventListener('click', () => {
            this.openSelectionModal()
          })

          // 弹框事件
          this.modalClose.addEventListener('click', () => {
            this.closeSelectionModal()
          })
          this.cancelSelection.addEventListener('click', () => {
            this.closeSelectionModal()
          })
          this.confirmSelection.addEventListener('click', () => {
            this.confirmAndRecognize()
          })
          this.modalOverlay.addEventListener('click', (e) => {
            if (e.target === this.modalOverlay) {
              this.closeSelectionModal()
            }
          })

          // 控制按钮事件
          this.cropAndRecognizeBtn.addEventListener('click', () => {
            if (!this.imageData) {
              this.showStatus('请先上传图片', 'error')
              return
            }
            this.openSelectionModal()
          })
          this.resetCanvasBtn.addEventListener('click', () =>
            this.resetCanvas()
          )
          this.copyBtn.addEventListener('click', () => this.copyResult())
          this.downloadBtn.addEventListener('click', () =>
            this.downloadResult()
          )
          this.pasteFromClipboardBtn.addEventListener('click', () =>
            this.pasteFromClipboard()
          )

          // Canvas鼠标事件（在弹框中）
          this.canvas.addEventListener('mousedown', (e) =>
            this.startSelection(e)
          )
          this.canvas.addEventListener('mousemove', (e) =>
            this.updateSelection(e)
          )
          this.canvas.addEventListener('mouseup', (e) => this.endSelection(e))
          this.canvas.addEventListener('mouseleave', (e) =>
            this.endSelection(e)
          )

          // Canvas触摸事件（移动端支持）
          this.canvas.addEventListener('touchstart', (e) =>
            this.handleTouchStart(e)
          )
          this.canvas.addEventListener('touchmove', (e) =>
            this.handleTouchMove(e)
          )
          this.canvas.addEventListener('touchend', (e) =>
            this.handleTouchEnd(e)
          )
          this.canvas.addEventListener('touchcancel', (e) =>
            this.handleTouchEnd(e)
          )
        }

        async initTesseract() {
          try {
            this.showStatus('正在初始化OCR引擎...', 'info')
            this.worker = await Tesseract.createWorker('chi_sim', 1, {
              workerPath: `https://www.996day.com/ocr/worker.min.js`,
              langPath: `https://www.996day.com/ocr`,
              corePath: `https://www.996day.com/ocr`,
            })
            this.showStatus('OCR引擎初始化完成', 'success')
          } catch (error) {
            console.error('Tesseract初始化失败:', error)
            this.showStatus('OCR引擎初始化失败', 'error')
          }
        }

        handleFileSelect(event) {
          const file = event.target.files[0]
          if (file) {
            this.handleFile(file)
          }
        }

        handleFile(file) {
          if (!file.type.startsWith('image/')) {
            this.showStatus('请选择有效的图片文件', 'error')
            return
          }

          this.imageFile = file
          const reader = new FileReader()

          reader.onload = (e) => {
            const img = new Image()
            img.onload = () => {
              // 保存图片数据
              this.imageData = {
                originalWidth: img.width,
                originalHeight: img.height,
                image: img,
              }

              // 显示预览图片
              this.previewImage.src = e.target.result
              this.imageContainer.style.display = 'block'
              this.fileUpload.style.display = 'none'

              // 启用按钮
              this.resetCanvasBtn.disabled = false
              this.cropAndRecognizeBtn.disabled = false
              this.showStatus('图片上传成功，点击图片选择识别区域', 'success')
            }
            img.src = e.target.result
          }

          reader.readAsDataURL(file)
        }

        // 弹框相关方法
        openSelectionModal() {
          if (!this.imageData) {
            this.showStatus('请先上传图片', 'error')
            return
          }

          // 计算Canvas尺寸，保持图片比例
          const maxWidth = 800
          let { width, height } = this.imageData.image

          // 只限制宽度，高度保持图片实际比例
          if (width > maxWidth) {
            const ratio = maxWidth / width
            width *= ratio
            height *= ratio
          }

          // 设置Canvas尺寸
          this.canvas.width = width
          this.canvas.height = height
          this.canvas.style.width = width + 'px'
          this.canvas.style.height = height + 'px'

          // 绘制图片到Canvas
          this.ctx.clearRect(0, 0, width, height)
          this.ctx.drawImage(this.imageData.image, 0, 0, width, height)

          // 更新图片数据
          this.imageData.displayWidth = width
          this.imageData.displayHeight = height

          // 显示弹框
          this.modalOverlay.style.display = 'flex'
          this.showStatus('请拖拽选择要识别的区域', 'info')
        }

        closeSelectionModal() {
          this.modalOverlay.style.display = 'none'
          this.selectionOverlay.style.display = 'none'
          this.isSelecting = false
          this.canvas.style.cursor = 'default'
        }

        confirmAndRecognize() {
          if (
            !this.selectionOverlay.style.display ||
            this.selectionOverlay.style.display === 'none'
          ) {
            this.showStatus('请先选择识别区域', 'error')
            return
          }

          // 关闭弹框
          this.closeSelectionModal()

          // 执行识别
          this.cropAndRecognize()
        }

        // Canvas区域选择方法
        startSelection(e) {
          if (!this.imageData) return

          const rect = this.canvas.getBoundingClientRect()
          this.startX = e.clientX - rect.left
          this.startY = e.clientY - rect.top
          this.endX = this.startX
          this.endY = this.startY
          this.isSelecting = true

          // 立即显示选择框（初始大小为0）
          this.selectionOverlay.style.display = 'block'
          this.selectionOverlay.style.left = this.startX + 'px'
          this.selectionOverlay.style.top = this.startY + 'px'
          this.selectionOverlay.style.width = '0px'
          this.selectionOverlay.style.height = '0px'

          // 改变鼠标样式
          this.canvas.style.cursor = 'crosshair'

          this.showStatus('正在选择区域...', 'info')
        }

        // 触摸事件处理方法
        handleTouchStart(e) {
          e.preventDefault()
          const touch = e.touches[0]
          const mouseEvent = new MouseEvent('mousedown', {
            clientX: touch.clientX,
            clientY: touch.clientY
          })
          this.startSelection(mouseEvent)
        }

        handleTouchMove(e) {
          e.preventDefault()
          if (!this.isSelecting) return
          const touch = e.touches[0]
          const mouseEvent = new MouseEvent('mousemove', {
            clientX: touch.clientX,
            clientY: touch.clientY
          })
          this.updateSelection(mouseEvent)
        }

        handleTouchEnd(e) {
          e.preventDefault()
          if (!this.isSelecting) return
          const mouseEvent = new MouseEvent('mouseup', {})
          this.endSelection(mouseEvent)
        }

        updateSelection(e) {
          if (!this.isSelecting || !this.imageData) return

          const rect = this.canvas.getBoundingClientRect()
          this.endX = e.clientX - rect.left
          this.endY = e.clientY - rect.top

          // 计算选择框的位置和尺寸
          const left = Math.min(this.startX, this.endX)
          const top = Math.min(this.startY, this.endY)
          const width = Math.abs(this.endX - this.startX)
          const height = Math.abs(this.endY - this.startY)

          // 更新选择框样式
          this.selectionOverlay.style.display = 'block'
          this.selectionOverlay.style.left = left + 'px'
          this.selectionOverlay.style.top = top + 'px'
          this.selectionOverlay.style.width = width + 'px'
          this.selectionOverlay.style.height = height + 'px'
        }

        endSelection(e) {
          if (!this.isSelecting) return

          this.isSelecting = false

          // 恢复鼠标样式
          this.canvas.style.cursor = 'default'

          // 如果选择区域太小，隐藏选择框
          const width = Math.abs(this.endX - this.startX)
          const height = Math.abs(this.endY - this.startY)

          if (width < 10 || height < 10) {
            this.selectionOverlay.style.display = 'none'
            this.showStatus('选择区域太小，请重新选择', 'warning')
            return
          }

          this.showStatus('区域选择完成，点击"裁剪并识别"开始处理', 'success')
        }

        // 获取裁剪后的Canvas
        getCroppedCanvas() {
          if (!this.imageData) return null

          // 计算裁剪区域
          const left = Math.min(this.startX, this.endX)
          const top = Math.min(this.startY, this.endY)
          const width = Math.abs(this.endX - this.startX)
          const height = Math.abs(this.endY - this.startY)

          // 计算在原始图片中的比例
          const scaleX =
            this.imageData.originalWidth / this.imageData.displayWidth
          const scaleY =
            this.imageData.originalHeight / this.imageData.displayHeight

          // 计算原始图片中的裁剪区域
          const cropX = left * scaleX
          const cropY = top * scaleY
          const cropWidth = width * scaleX
          const cropHeight = height * scaleY

          // 创建新的Canvas用于裁剪
          const croppedCanvas = document.createElement('canvas')
          const croppedCtx = croppedCanvas.getContext('2d')

          // 设置裁剪后的Canvas尺寸
          croppedCanvas.width = cropWidth
          croppedCanvas.height = cropHeight

          // 绘制裁剪区域到新Canvas
          croppedCtx.drawImage(
            this.imageData.image,
            cropX,
            cropY,
            cropWidth,
            cropHeight,
            0,
            0,
            cropWidth,
            cropHeight
          )

          return croppedCanvas
        }

        async cropAndRecognize() {
          if (!this.imageData) {
            this.showStatus('请先上传图片', 'error')
            return
          }

          if (!this.worker) {
            this.showStatus('OCR引擎未初始化完成，请稍后再试', 'error')
            return
          }

          // 检查是否有选择区域
          const width = Math.abs(this.endX - this.startX)
          const height = Math.abs(this.endY - this.startY)

          if (width < 10 || height < 10) {
            this.showStatus('请先选择要识别的区域', 'error')
            return
          }

          try {
            this.showProgress(true)
            this.updateProgress(10, '正在裁剪图片...')

            // 创建裁剪后的canvas
            const croppedCanvas = this.getCroppedCanvas()
            if (!croppedCanvas) {
              this.showStatus('裁剪失败，请重新选择区域', 'error')
              return
            }

            this.updateProgress(30, '准备识别文字...')

            // 设置语言
            const language = this.languageSelect.value
            await this.worker.loadLanguage(language)
            await this.worker.initialize(language)

            // 设置页面分割模式
            const psm = this.psmSelect.value
            await this.worker.setParameters({
              tessedit_pageseg_mode: psm,
            })

            this.updateProgress(50, '正在识别文字...')

            // 转换canvas为blob进行识别
            const blob = await new Promise((resolve) => {
              croppedCanvas.toBlob(resolve, 'image/png')
            })

            const {
              data: { text, confidence },
            } = await this.worker.recognize(blob)

            this.updateProgress(90, '处理识别结果...')

            // 显示结果
            this.resultText.value = text.trim()
            this.confidenceInfo.style.display = 'block'
            this.confidenceValue.textContent = `${Math.round(confidence)}%`

            this.updateProgress(100, '识别完成!')
            this.showStatus('文字识别完成', 'success')

            // 启用复制和下载按钮
            this.copyBtn.disabled = false
            this.downloadBtn.disabled = false

            setTimeout(() => {
              this.showProgress(false)
            }, 1000)
          } catch (error) {
            console.error('识别失败:', error)
            this.showStatus('识别失败: ' + error.message, 'error')
            this.showProgress(false)
          }
        }

        resetCanvas() {
          // 重置所有状态
          this.imageData = null
          this.imageFile = null

          // 隐藏图片容器，显示上传区域
          this.imageContainer.style.display = 'none'
          this.fileUpload.style.display = 'block'

          // 关闭弹框
          this.modalOverlay.style.display = 'none'
          this.selectionOverlay.style.display = 'none'

          // 重置选择状态
          this.startX = 0
          this.startY = 0
          this.endX = 0
          this.endY = 0
          this.isSelecting = false

          // 清空预览图片
          this.previewImage.src = ''

          // 禁用按钮
          this.resetCanvasBtn.disabled = true

          // 清空结果
          this.resultText.value = ''
          this.confidenceSpan.textContent = ''

          this.showStatus('已重置，请重新上传图片', 'success')
        }

        copyResult() {
          if (this.resultText.value) {
            navigator.clipboard
              .writeText(this.resultText.value)
              .then(() => {
                this.showStatus('文本已复制到剪贴板', 'success')
              })
              .catch(() => {
                // 降级方案
                this.resultText.select()
                document.execCommand('copy')
                this.showStatus('文本已复制到剪贴板', 'success')
              })
          }
        }

        async pasteFromClipboard() {
          try {
            // 检查浏览器是否支持剪切板API
            if (!navigator.clipboard || !navigator.clipboard.read) {
              this.showStatus('当前浏览器不支持剪切板功能', 'error')
              return
            }

            this.showStatus('正在读取剪切板...', 'info')
            
            // 读取剪切板内容
            const clipboardItems = await navigator.clipboard.read()
            
            for (const clipboardItem of clipboardItems) {
              // 检查是否包含图片类型
              const imageTypes = clipboardItem.types.filter(type => type.startsWith('image/'))
              
              if (imageTypes.length > 0) {
                // 获取第一个图片类型
                const imageType = imageTypes[0]
                const blob = await clipboardItem.getType(imageType)
                
                // 创建File对象
                const file = new File([blob], 'clipboard-image.' + imageType.split('/')[1], {
                  type: imageType
                })
                
                // 使用现有的handleFile方法处理图片
                this.handleFile(file)
                this.showStatus('剪切板图片已加载', 'success')
                return
              }
            }
            
            // 如果没有找到图片
            this.showStatus('剪切板中没有图片内容', 'warning')
            
          } catch (error) {
            console.error('读取剪切板失败:', error)
            if (error.name === 'NotAllowedError') {
              this.showStatus('请允许访问剪切板权限', 'error')
            } else {
              this.showStatus('读取剪切板失败: ' + error.message, 'error')
            }
          }
        }

        downloadResult() {
          if (this.resultText.value) {
            const blob = new Blob([this.resultText.value], {
              type: 'text/plain;charset=utf-8',
            })
            const url = URL.createObjectURL(blob)
            const a = document.createElement('a')
            a.href = url
            a.download = 'ocr_result.txt'
            document.body.appendChild(a)
            a.click()
            document.body.removeChild(a)
            URL.revokeObjectURL(url)
            this.showStatus('结果已下载', 'success')
          }
        }

        showProgress(show) {
          this.progressContainer.style.display = show ? 'flex' : 'none'
        }

        updateProgress(percent, text) {
          this.progressFill.style.width = percent + '%'
          this.progressText.textContent = text
        }

        showStatus(message, type = 'info') {
          this.statusMessage.textContent = message
          this.statusMessage.className = `status-message ${type} show`

          setTimeout(() => {
            this.statusMessage.classList.remove('show')
          }, 3000)
        }
      }

      // 初始化应用
      const ocrTool = new OCRTool()
    </script>
  </body>
</html>
